﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SimpleIR;
using System.Diagnostics.Contracts;

namespace RgnAnalyzer.Domain
{
	/// <summary>
	/// This class is used to represent container object values (just maps of fields to types).
	/// </summary>
	internal sealed class AContainer : AObject
	{
		/// <summary>
		/// True if the contianer must be empty.
		/// </summary>
		private bool m_mustbeempty;

		internal AContainer(int address, IRContainerType tt, bool isempty, bool uniqueloc, int[] identity, EscapeSiteInfo[] esclocinfo, List<UseModInfo> uml, APtrValueMap fvalues)
			: base(address, tt, uniqueloc, identity, esclocinfo, uml, fvalues)
		{ this.m_mustbeempty = isempty; }

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder("ContainerObj " + this.m_loctype.ToString() + " ");
			if(this.IsUniqueLocation())
				sb.Append("@UL ");
			else
				sb.Append("@ML ");

			if(this.m_mustbeempty)
				sb.Append("Empty ");
			
			sb.Append(this.m_ptrinfo.ToString());

			sb.Append(this.ComputeGeneralInfoString());

			return sb.ToString();
		}

		/// <summary>
		/// Return true if this container must be empty.
		/// </summary>
		internal bool MustBeEmpty
		{ get { return this.m_mustbeempty; } }

		internal override ALocation CopyLocation()
		{
			int[] ida = (this.m_identities == EmptyIdentity ? EmptyIdentity : (int[])this.m_identities.Clone());
			EscapeSiteInfo[] fli = (this.m_escapelines == EmptyAllocLines ? ALocation.EmptyAllocLines : (EscapeSiteInfo[])this.m_escapelines.Clone());
			List<UseModInfo> umv = new List<UseModInfo>(this.m_uml);

			return new AContainer(this.m_locationaddress, (IRContainerType)this.m_loctype, this.m_mustbeempty, this.m_uniquelocation, ida, fli, umv, this.m_ptrinfo);
		}

		internal override AObject SimulateObjectClone(List<ALocation> memory, int line)
		{
			int newaddress = memory.Count;
			EscapeSiteInfo[] escinfo = new EscapeSiteInfo[] { new EscapeSiteInfo(line, line, line) };
			APtrValueMap ppa = this.m_ptrinfo.SetAllAsInjectiveAsNeededForUniqueLocation(this.LocationType);

			AContainer resobj = new AContainer(newaddress, (IRContainerType)this.m_loctype, this.m_mustbeempty, true, EmptyIdentity, escinfo, new List<UseModInfo>(), ppa);
			UseModInfo.MarkUMIAtObjectAllocation((IRClassType)resobj.m_loctype, resobj.m_uml, line);

			memory.Add(resobj);
			return resobj;
		}

		/// <summary>
		/// Given a type create the default value for it.
		/// </summary>
		internal static AContainer AllocateContainerOfType(List<ALocation> memory, IRContainerType tt, int line)
		{
			int address = memory.Count;

			EscapeSiteInfo[] escinfo = new EscapeSiteInfo[] { new EscapeSiteInfo(line, line, line) };

			List<IRBasicAccessPath> maynulllocs = new List<IRBasicAccessPath>(tt.TypeDescription.AllTerminalAccessPaths);
			if(!tt.IsArrayContainer)
				maynulllocs.RemoveAll((IRBasicAccessPath bap) => bap.PathFirst.IsSummaryField);

			AContainer res = new AContainer(address, tt, !tt.IsArrayContainer, true, ALocation.EmptyIdentity, escinfo, new List<UseModInfo>(), APtrValueMap.MakeInitalPtrValueMapComposite(maynulllocs));
			UseModInfo.MarkUMIAtObjectAllocation((IRClassType)res.m_loctype, res.m_uml, line);

			memory.Add(res);
			return res;
		}

		internal bool IsKnownEmpty()
		{
			return this.m_mustbeempty;
		}

		internal Nullable<APtrValueMap> DoContainerContentsReadFromLocationBI(IRBasicAccessPath bap, int line)
		{
			if(bap.PathLast.FieldType is IRNumericType | bap.PathLast.FieldType is IREnumType)
			{
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, false, line, bap);
				return null;
			}
			else if(bap.PathLast.FieldType is IRPtrType)
			{
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, false, line, bap);

				return this.m_ptrinfo.ReadPtrValues(bap);
			}
			else
			{
				foreach(IRBasicAccessPath sbap in bap.PathLast.FieldType.TypeDescription.AllTerminalAccessPaths)
				{
					IRBasicAccessPath fsbap = GlobalAnalysisState.TProg.GetAccessPathExtensionPost_WPath(bap, sbap);
					UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, false, line, fsbap);
				}

				return this.m_ptrinfo.ReadPtrValues(bap);
			}
		}

		/// <summary>
		/// Do a write to a container summary field as support of builtin operations. This allows us to strongly update container summary fields 
		/// in very controlled situations.
		/// </summary>
		internal void DoContainerContentsWriteToLocationBI(IRBasicAccessPath bap, APtrValueMap pvals, int line, IRMemberField knowninjfield)
		{
			if(bap.PathLast.FieldType is IRNumericType | bap.PathLast.FieldType is IREnumType)
			{
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), false, true, line, bap);
			}
			else if(bap.PathLast.FieldType is IRPtrType)
			{
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), false, true, line, bap);

				this.m_ptrinfo = this.m_ptrinfo.WritePtrValuesContainerBI(pvals, bap, this.DoStrongUpdate(true), (this.m_uniquelocation ? knowninjfield : null));
			}
			else
			{
				foreach(IRBasicAccessPath sbap in bap.PathLast.FieldType.TypeDescription.AllTerminalAccessPaths)
				{
					IRBasicAccessPath fsbap = GlobalAnalysisState.TProg.GetAccessPathExtensionPost_WPath(bap, sbap);
					UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), false, true, line, fsbap);
				}

				this.m_ptrinfo = this.m_ptrinfo.WritePtrValuesContainerBI(pvals, bap, this.DoStrongUpdate(true), (this.m_uniquelocation ? knowninjfield : null));
			}

			this.m_mustbeempty = false;
		}

		/// <summary>
		/// Do a reorg to a container summary field as support of builtin operations. Resize, sorting, etc.
		/// </summary>
		internal void DoContainerContentsReorgBI(IRBasicAccessPath bap, int line)
		{
			if(bap.PathLast.FieldType is IRNumericType | bap.PathLast.FieldType is IREnumType)
			{
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, true, line, bap);
			}
			else if(bap.PathLast.FieldType is IRPtrType)
			{
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, true, line, bap);
			}
			else
			{
				foreach(IRBasicAccessPath sbap in bap.PathLast.FieldType.TypeDescription.AllTerminalAccessPaths)
				{
					IRBasicAccessPath fsbap = GlobalAnalysisState.TProg.GetAccessPathExtensionPost_WPath(bap, sbap);
					UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, true, line, fsbap);
				}
			}
		}

		/// <summary>
		/// Do a remove from a container summary field as support of builtin operations.
		/// </summary>
		internal void DoContainerContentsRemoveBI(IRBasicAccessPath bap, int line)
		{
			if(bap.PathLast.FieldType is IRNumericType | bap.PathLast.FieldType is IREnumType)
			{
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, true, line, bap);
			}
			else if(bap.PathLast.FieldType is IRPtrType)
			{
				UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, true, line, bap);
			}
			else
			{
				foreach(IRBasicAccessPath sbap in bap.PathLast.FieldType.TypeDescription.AllTerminalAccessPaths)
				{
					IRBasicAccessPath fsbap = GlobalAnalysisState.TProg.GetAccessPathExtensionPost_WPath(bap, sbap);
					UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), true, true, line, fsbap);
				}
			}

			this.m_mustbeempty = false;
		}

		/// <summary>
		/// Do a clear of a container summary field as support of builtin operations.
		/// </summary>
		internal void DoContainerContentsClearBI(IRBasicAccessPath bap, int line)
		{
			if(this.DoStrongUpdate(true))
			{
				if(bap.PathLast.FieldType is IRNumericType | bap.PathLast.FieldType is IREnumType)
				{
					UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), false, true, line, bap);
				}
				else if(bap.PathLast.FieldType is IRPtrType)
				{
					UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), false, true, line, bap);
				}
				else
				{
					foreach(IRBasicAccessPath sbap in bap.PathLast.FieldType.TypeDescription.AllTerminalAccessPaths)
					{
						IRBasicAccessPath fsbap = GlobalAnalysisState.TProg.GetAccessPathExtensionPost_WPath(bap, sbap);
						UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), false, true, line, fsbap);
					}
				}

				this.m_ptrinfo = APtrValueMap.MakeInitalPtrValueMapComposite(new List<IRBasicAccessPath>());
				this.m_mustbeempty = true;
			}
			else
			{
				if(bap.PathLast.FieldType is IRNumericType | bap.PathLast.FieldType is IREnumType)
				{
					UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), false, true, line, bap);
				}
				else if(bap.PathLast.FieldType is IRPtrType)
				{
					UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), false, true, line, bap);
				}
				else
				{
					foreach(IRBasicAccessPath sbap in bap.PathLast.FieldType.TypeDescription.AllTerminalAccessPaths)
					{
						IRBasicAccessPath fsbap = GlobalAnalysisState.TProg.GetAccessPathExtensionPost_WPath(bap, sbap);
						UseModInfo.MarkUMI(this.m_uml, this.MayBeInternalAlloc(), this.MayBeExternalAlloc(), false, true, line, fsbap);
					}
				}
			}
		}

		protected override bool EqualALocationSubclass(ALocation ol)
		{
			if(!(ol is AContainer))
				return false;

			if(this.m_mustbeempty != ((AContainer)ol).m_mustbeempty)
				return false;

			return base.EqualALocationSubclass(ol);
		}

		protected override ALocation JoinLocationsFromDifferentContextsSubclass(ALocation ol, int[] residentities, EscapeSiteInfo[] resfreshlocs, List<UseModInfo> resuml)
		{
			Contract.Assert(ol is AContainer);
			AContainer col = (AContainer)ol;

			bool stillempty = (this.m_mustbeempty & col.m_mustbeempty);
			bool stillunique = (this.m_uniquelocation & col.m_uniquelocation);

			APtrValueMap ppa;
			if(this.m_mustbeempty)
				ppa = col.m_ptrinfo;
			else if(col.m_mustbeempty)
				ppa = this.m_ptrinfo;
			else
				ppa = APtrValueMap.MergeAPtrMaps(this.m_ptrinfo, col.m_ptrinfo, true, stillunique);

			return new AContainer(this.m_locationaddress, (IRContainerType)this.m_loctype, stillempty, stillunique, residentities, resfreshlocs, resuml, ppa);
		}

		protected override ALocation JoinLocationSetFromSameContextSubclass(List<ALocation> lset, int resaddress, int[] residentities, EscapeSiteInfo[] resfreshlocs, List<UseModInfo> resuml, Dictionary<int, int> addrReMap)
		{
			Nullable<APtrValueMap> acclm = null;
			bool stillempty = true;

			foreach(ALocation ll in lset)
			{
				Contract.Assert(ll is AContainer);
				AContainer cll = (AContainer)ll;

				stillempty &= cll.m_mustbeempty;

				if(!acclm.HasValue)
					acclm = cll.m_ptrinfo;
				else
					acclm = APtrValueMap.MergeAPtrMaps(acclm.Value, cll.m_ptrinfo, false, false);
			}

			acclm = acclm.Value.UpdatedAddresses_MayMergeButNoNewInj(addrReMap, false);
			return new AContainer(resaddress, (IRContainerType)this.m_loctype, stillempty, false, residentities, resfreshlocs, resuml, acclm.Value);
		}
	}
}
